<?php
session_start();
require 'php-captcha.inc.php';
require 'phpass/PasswordHash.php';
require 'config.php';
require 'anti_csrf.php';

function is_pw_notweak($pw) {
    if (strlen($pw) < 8) return FALSE;
    if (preg_match('/[A-Z]/', $pw) !== 1) return FALSE;
    if (preg_match('/[a-z]/', $pw) !== 1) return FALSE;
    if (preg_match('/[0-9]/', $pw) !== 1) return FALSE;
    return TRUE;
}

function encrypt_base64_str($str, $td, $key, $iv) {
    // Intialize encryption
    mcrypt_generic_init($td, $key, $iv);

    // Encrypt data
    $encrypted = mcrypt_generic($td, $str);

    // Terminate encryption handler
    mcrypt_generic_deinit($td);

    return base64_encode($encrypted);
}

function decrypt_base64_str($str, $td, $key, $iv) {
    mcrypt_generic_init($td, $key, $iv);
    $decrypted = mdecrypt_generic($td, base64_decode($str));
    mcrypt_generic_deinit($td);
    return $decrypted;
}

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if (PhpCaptcha::Validate($_POST['signup_captcha'])) {
        if (!isset($_POST['CSRFName'])) {
            trigger_error("No CSRFName found, probable invalid request.",E_USER_ERROR);
        }
        $name =$_POST['CSRFName'];
        $token=$_POST['CSRFToken'];
        if (!csrfguard_validate_token($name, $token)) {
            trigger_error("Invalid CSRF token.",E_USER_ERROR);
        }

        $con = pg_connect($dbconstr);
        if ($con == FALSE) {
            die('Unable to connect to database.');
        }

        // get data from POST data
        $username = $_POST['username'];
        $password = $_POST['password'];
        $realname = $_POST['realname'];
        $email = $_POST['emailaddr'];
        $realid = $_POST['ID'];
        $streetaddress = $_POST['streetaddr'];
        $zipcode = $_POST['zipcode'];
        $phone = $_POST['phonenum'];

        // check username syntax
        $uname_regex = '/^\w{1,'.USERNAME_MAX_CHARS.'}$/';
        if (preg_match($uname_regex, $username) !== 1) {
            die("Username format incorrect.\nRequires 1-'.USERNAME_MAX_CHARS.' alphanumeric or underscore (_) characters.");
        }

        // check password length and strength
        if (strlen($password) > 70) {
            die('Password too long. Requires no longer than 70 characters.');
        }
        if (is_pw_notweak($password) == FALSE) {
            die("Insufficient password strength.\nRequires at least 8 characters, with at least 1 upper-case letter, 1 lower-case letter, and 1 digit.");
        }

        // check real name syntax
        if (preg_match('/^[A-Za-z0-9 ,.\'"\\-]{1,500}$/', $realname) !== 1) {
            die("Real name format incorrect.\nOnly alphanumeric characters, spaces, single and double quotes, ampersands, commas, periods, and hyphens are allowed.\nCannot be longer than 500 characters.");
        }

        // check email address syntax
        if (preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/', $email) !== 1) {
            die('Email address format incorrect.');
        }

        // check (real) ID syntax
        if (preg_match('/^[A-Za-z0-9 \\-.]{1,100}$/', $realid) !== 1) {
            die('Real ID format incorrect. Can only include alphanumeric characters, spaces, hyphens, and dots. Cannot be longer than 100 characters.');
        }

        // check street address syntax
        if (preg_match('/^[A-Za-z0-9 ,.\'"\\-]{1,500}$/', $streetaddress) !== 1) {
            die('Street address format incorrect. Can only include alphanumeric characters, spaces, commas, dots, single quotes, double quotes, and hyphens. Cannot be longer than 500 characters.');
        }

        // check zip code syntax
        if (preg_match('/^[0-9]{5}(\\-[0-9]{4})?$/', $zipcode) !== 1) {
            die("Zip code format incorrect.\nRequires 5 digits or \"5-4\" format.");
        }

        // check phone syntax
        if (preg_match('/^[0-9]{10}$/', $phone) !== 1) {
            die("Phone number format incorrect.\nRequires 10 digits.");
        }

        // hash and salt the password using phpass
        $hasher = new PasswordHash($hash_cost_log2, $hash_portable);
        $hash = $hasher->HashPassword($password);
        if (strlen($hash) < 20) {
            die('Unable to hash password.');
        }
        unset($hasher);

        // encrypt real name and ID
        $td = mcrypt_module_open('rijndael-256', '', 'ofb', '');

        // create IV
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_DEV_RANDOM);
        $ks = mcrypt_enc_get_key_size($td);

        // create key
        $key = substr(sha1($password), 0, $ks);

        $realname = encrypt_base64_str($realname, $td, $key, $iv);
        $realid = encrypt_base64_str($realid, $td, $key, $iv);
        $streetaddress = encrypt_base64_str($streetaddress, $td, $key, $iv);
        $zipcode = encrypt_base64_str($zipcode, $td, $key, $iv);
        $phone = encrypt_base64_str($phone, $td, $key, $iv);

        $iv = base64_encode($iv);

        mcrypt_module_close($td);

        $result = pg_prepare($con, 'signup_query', 'INSERT INTO account (username, pwhash, realname, email, realid, streetaddress, zipcode, phone, iv) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)');
        if ($result == FALSE) {
            die('Unable to prepare statement.');
        }
        $result = pg_execute($con, 'signup_query', array($username, $hash, $realname, $email, $realid, $streetaddress, $zipcode, $phone, $iv));
        if ($result == FALSE) {
            die('Unable to execute query.');
        }
        $_SESSION['username'] = $username;

        header('Location: /php/account.php?id='.$username);
        
        pg_close($con);
    } else {
        echo 'Invalid code entered';
    }
}
?>
